Silverlight – About validation when binding to custom forms

I have notice that some people have problems about how to implement User Input validation when they don’t use controls like the DataForm or DataGrid  etc, and instead bind directly to a TextBox or other user input controls. In this post I will write about different ways to handle validations. In this post I will work with a simple View. I will later add a post where I will use some new Validation features in Siverlight 4 and where I also use the MVVM pattern and Commanding.

The following is the XAML of the View I will use in this post:

<UserControl ... >

    <Grid x:Name="LayoutRoot">

        <StackPanel Width="400" Margin="50">
            <TextBox
x:Name="myTextBox"
Text="{Binding Name, Mode=TwoWay, ValidatesOnExceptions=true}"/>
            <Button Margin="0,10,0,0" Click="Button_Click" Content="Save"/>
        </StackPanel>
      
  </Grid>
</UserControl>

Here is the View in runtime when a validation fails:

image

Hmm, is the template of the Validation tooltip changed? ;)

I have created a simple class called Customer which I bind to the LayoutRoot’s DataContext property in the code-behind. Here is the Customer class:

public class Customer
{
    private string _name = null;
        
    public string Name
    {
        get { return _name; }
        set
        {
            if (string.IsNullOrEmpty(value))
                throw new ValidationException("The Name field is required");
            _name = value;
        }
    }
}

 

Here is the code-behind where I set the LayoutRoot’s DataContext to a new empty Customer class:

public partial class MainPage : UserControl
{
    public MainPage()
    {
        InitializeComponent();

        LayoutRoot.DataContext = new Customer(); ;
     }
}


When the Customer’s Name property is set a validation will take place to check if the value is null or empty. If the value is empty a ValidationException will be thrown (You can use any kind of exception class here, but I use the ValidationException in this example). When a TwoWay binding is used on a TextBox, the set property of the bounded property will only be set when the TextBox lost focus (this is by default and can be changed); BUT! Only as long as the TextBox value is changed from its previous value. If the TextBox value is an empty string when the application is started, the validation will not take place if we left the TextBox blank, even if the TextBox will lost is focus the set property will not be set. To make sure so it will be set, the TextBox value must be changed. I have seen several questions on forums where developers have problem with a required value validation because of this. So how to handle this? One way is to change the Binding’s UpdateSourceTrigger to Explicit and then manually trigger the update of the source with the BindingExpression, for example when the TextBox will lost is focus or when the user click’s on a Button. Here is an example using the LostFocus event:

private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
     BindingExpression expression = myTextBox.GetBindingExpression(TextBox.TextProperty);
     expression.UpdateSource();
}


By doing the UpdateSource manually you can make sure the set property bound to the TextBox will always be set even if the value isn’t changed from its previous value.

To make sure the nice built in validation tooltip will appear when a bounded property will throw an exception, the Binding expression’s ValidatesOnExceptions must be set to true, if not, the tooltip will not be displayed and the user will not know if the validation failed.

<TextBox x:Name="myTextBox"Text="{Binding Name, Mode=TwoWay, ..., ValidatesOnExceptions=true}">


Instead of throwing exceptions and add validation code to the set property, annotation can also be used.

Using Validation Annotation

Here is an example of the Customer class using validation annotation instead of an if statement:

public class Customer
{
   private string _name = null;
        
    [Required]
    public string Name
    {
        get { return _name; }
        set
        {
             //if (string.IsNullOrEmpty(value))
             //       throw new ValidationException("The Name field is required");

             Validator.ValidateProperty(value,
                          new ValidationContext(this, null, null) { MemberName = "Name" });

             _name = value;
        }
    }
}

 

The code above only uses the Require validation attribute but there are more attributes, like the RegularExpression-, Range- and StringLengthAttribute etc. You can find them in the System.ComponentModel.DataAnnotations namespace. If you want to make sure the validation annotation should be used the Validator’s ValidateProperty method must called within the set method of the property with the annotation. The ValidateProperty method will throw a ValidationException if the validation is invalid.

If you don’t want to use the explicit handling of updating a source with the BindingExpression’s UpdateSource() method, you can validate the whole Customer object when you for example press a button. This will also reduce codes both within the code-behind and XAML. If you have several TextBoxes in the View, it will be really boring to hook up to the LostFocus event and manually update the source with the BindingExpression’s UpdateSource method. By validating a whole object you can use the Validator.TryValidateObject method. Here is an example where the whole Customer object is validated when a Button is clicked:

private void Button_Click(object sender, RoutedEventArgs e)
{
    var validationContext = new ValidationContext(LayoutRoot.DataContext, null, null);
    List<ValidationResult> validationResults = new List<ValidationResult>();

    if (!Validator.TryValidateObject(LayoutRoot.DataContext, validationContext, validationResults))
    {
         //Validation failed
    }
    else
    {
         //Valdation passed    
    }
}


The validationResults will contain a list of the validation results when the validation failed. The problem now is to make sure the TextBox’s validation tooltip will be visible if the validation fails and also show the validation error message. After some tries I managed to make it work, and also as a result I managed to change the ControlTemplate for the validation tooltip. The following is the code to show the validation tooltip for the TextBox (myTextBox) added in this post example and also set the validation message for the tooltip:

private void Button_Click(object sender, RoutedEventArgs e)
{
    var validationContext = new ValidationContext(LayoutRoot.DataContext, null, null);
    List<ValidationResult> validationResults = new List<ValidationResult>();

     if (!Validator.TryValidateObject(LayoutRoot.DataContext, validationContext, validationResults))
     {
          var border = ((Border)VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(myTextBox, 0), 3));
          var tooltip = border.GetValue(ToolTipService.ToolTipProperty) as ToolTip;

          tooltip.DataContext = validationResults[0].ErrorMessage;

          tooltip.Template = this.Resources["ValidationToolTipTemplate"] as ControlTemplate;

           if (!myTextBox.Focus())
               VisualStateManager.GoToState(myTextBox, "InvalidUnfocused", true);
           else
               VisualStateManager.GoToState(myTextBox, "InvalidFocused", true);
       }
       else
           VisualStateManager.GoToState(myTextBox, "Valid", true);
}


Note: I haven’t done any refactoring here and the code is not generic, the myTextBox is hardcoded in the code above. The code is only to show the concept.

By using the VisualTreeHelper I managed to get the ToolTip showing the validation error message. I changed the ToolTip’s template to my own and also changed the DataContext of the tooltip to the validation error message. By default the TextBlock used to show the error message within the ToolTip Template is bound to the Validation.Errors attached property, but the constructor of the ValidationError class used by the Errors collection is internal; so I couldn’t use it and add ValidationErrors to the attached property, so I just use the ToolTip’s DataContext instead. The default TextBox template has some visual states to make a border and the the tooltip visible or not, InvalidUnfocused, InvalidFocused and Valid, I decided to reuse them.

The ValidationToolTipTemplate I use for the tooltip is here:

<UserControl.Resources>
    <ControlTemplate x:Key="ValidationToolTipTemplate">
        <Grid x:Name="Root" Margin="5,0" RenderTransformOrigin="0,0" Opacity="0">
            <Grid.RenderTransform>
                <TranslateTransform x:Name="xform" X="-25"/>
            </Grid.RenderTransform>
            <VisualStateManager.VisualStateGroups>
                <VisualStateGroup Name="OpenStates">
                    <VisualStateGroup.Transitions>
                        <VisualTransition GeneratedDuration="0"/>
                        <VisualTransition To="Open" GeneratedDuration="0:0:0.2">
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetName="xform"
                                                 Storyboard.TargetProperty="X"
                                                 To="0"
                                                 Duration="0:0:0.2">
                                    <DoubleAnimation.EasingFunction>
                                        <BackEase Amplitude=".3" EasingMode="EaseOut"/>
                                    </DoubleAnimation.EasingFunction>
                                </DoubleAnimation>
                                <DoubleAnimation Storyboard.TargetName="Root"
                                                 Storyboard.TargetProperty="Opacity"
                                                 To="1"
                                                 Duration="0:0:0.2"/>
                            </Storyboard>
                        </VisualTransition>
                    </VisualStateGroup.Transitions>
                    <VisualState x:Name="Closed">
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetName="Root"
                                             Storyboard.TargetProperty="Opacity"
                                             To="0"
                                             Duration="0"/>
                        </Storyboard>
                    </VisualState>
                    <VisualState x:Name="Open">
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetName="xform"
                                             Storyboard.TargetProperty="X"
                                             To="0"
                                             Duration="0"/>
                            <DoubleAnimation Storyboard.TargetName="Root"
                                             Storyboard.TargetProperty="Opacity"
                                             To="1"
                                             Duration="0"/>
                        </Storyboard>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>

            <Border Background="#FF000045" CornerRadius="2"/>
            <Border CornerRadius="2">
                <StackPanel Orientation="Horizontal" Margin="8,4,8,4">
                   <Image Source="validation.png" Height="20" VerticalAlignment="Center"/>
                   <TextBlock 
                       UseLayoutRounding="false" 
                       Foreground="White"
                       VerticalAlignment="Center"
                       FontWeight="Bold"
                       MaxWidth="250"
                       TextWrapping="Wrap"
                       Text="{Binding}"/>

                </StackPanel>
            </Border>
            <Grid.Effect>
                <DropShadowEffect ShadowDepth="1"/>
            </Grid.Effect>
        </Grid>
    </ControlTemplate>
</UserControl.Resources>


I think I made some people happy now when they can see how they can change the ToolTip template for a TextBox without adding the Template for the TextBox itself ;)

Summary

In this post you have seen different validation solutions like throwing exception, using annotation, solve the Required field validation and how to change the Validation Tooltip template.

 

If you want to know when I publish new blog posts you can follow me on twitter: http://www.twitter.com/fredrikn

4 Comments

Comments have been disabled for this content.